package com.apps.saspect.impl;


import com.apps.sannotation.service.SuportService;
import com.apps.saspect.ScriptEvent;
import com.apps.saspect.model.vo.ExcuteMethodVo;
import com.apps.sconfig.SDevFastConfig;
import com.apps.sconfig.enums.ScriptThreadType;
import com.apps.sexctption.DataException;
import com.apps.sexctption.ScriptException;
import com.apps.shelper.AspectHelper;
import com.apps.shelper.ClassHelper;
import com.apps.shelper.MethodHelper;
import com.apps.stoolkit.ApplicationContextToolkit;
import com.apps.sutils.AopTargetUtils;
import com.apps.sutils.StringUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.*;

@Slf4j
@Aspect
@Component
@Configuration
public class ScriptEventAspect {

    @Autowired(required = false)
    private SDevFastConfig sDevFastConfig;


    @Pointcut("@annotation(com.apps.saspect.ScriptEvent)")
    public void pointcut() {

    }

    /**
     * 环切执行脚本调用
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint point) throws Throwable {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        ScriptEvent scriptEvent=method.getAnnotation(ScriptEvent.class);
        if(null == scriptEvent)
            throw new ScriptException("方法未找到@ScriptEvent注解");
        //找到脚本的所有实现
        Map<String, ?> beans=ApplicationContextToolkit.applicationContext.getBeansOfType(scriptEvent.script());
        //如果没有任何实现直接返回
        if(beans.size()<=0)
            return point.proceed();
        //将所有实现及其方法压进map
        Map<String,Map<String,Method>> selfMethods= findMethods(beans,point.getTarget().getClass());
        //解析出传参列表
        Map<String, Object> parameters = AspectHelper.handlerParameter(point);

        //执行前置脚本执行
        if(StringUtils.notEmpty(scriptEvent.beForeMethodName()))
        {
            ExcuteMethodVo methodVo=CalculationMethodParams(scriptEvent.beForeMethodName(),parameters,null);
            for (String key : beans.keySet()) {
                //如果实现不存在
                if(null == selfMethods.get(key))
                    continue;
                //验证方法是否存在,如果是父类的方法则不需要执行.脚本只执行被使用者实现了的方法
                if(null == selfMethods.get(key).get(methodVo.getMethodName()))
                    continue;

                if(sDevFastConfig.getThreadType() == ScriptThreadType.AfterMultiThread)
                {
                    //调用方法
                    beans.get(key).getClass().getMethod(methodVo.getMethodName(), methodVo.getClasss()).invoke(beans.get(key),methodVo.getObjects());
                }else
                {
                    new Thread(new Runnable() {
                        @SneakyThrows
                        @Override
                        public void run() {
                            //调用方法
                            beans.get(key).getClass().getMethod(methodVo.getMethodName(), methodVo.getClasss()).invoke(beans.get(key), methodVo.getObjects());
                        }
                    }).start();
                }
            }
        }
        //真实执行方法
        Object result= point.proceed();
        //执行后置脚本,开独立线程执行
        if(StringUtils.notEmpty(scriptEvent.afterMethodName()))
        {
            ExcuteMethodVo methodVo=CalculationMethodParams(scriptEvent.afterMethodName(),parameters,result);
            for (String key : beans.keySet()) {
                //如果实现不存在
                if(null == selfMethods.get(key))
                    continue;
                //验证方法是否存在,如果是父类的方法则不需要执行.脚本只执行被使用者实现了的方法
                if(null == selfMethods.get(key).get(methodVo.getMethodName()))
                    continue;

                if(sDevFastConfig.getThreadType() == ScriptThreadType.BeforeMultiThread)
                {
                    beans.get(key).getClass().getMethod(methodVo.getMethodName(), methodVo.getClasss()).invoke(beans.get(key), methodVo.getObjects());
                }
                else {
                    //调用方法
                    new Thread(new Runnable() {

                        @SneakyThrows
                        @Override
                        public void run() {
                            beans.get(key).getClass().getMethod(methodVo.getMethodName(), methodVo.getClasss()).invoke(beans.get(key), methodVo.getObjects());
                        }
                    }).start();
                }
            }
        }
        //返回值
        return result;
    }

    /**
     * 从spring找出所有实现,然后把每个实现的方法弄成map
     * @param beans
     * @param that
     * @return
     */
    @SneakyThrows
   protected Map<String,Map<String,Method>> findMethods(Map<String, ?> beans,Class<?> that)
   {
       Map<String,Map<String,Method>> result = new HashMap<>();

       for(String key:beans.keySet())
       {
           if(!isExecuteScript(that, AopTargetUtils.getTarget(beans.get(key)).getClass()))
               continue;
           //将自己的所有方法压进map备用
           result.put(key,MethodHelper.getMethodNamesMap(AopTargetUtils.getTarget(beans.get(key)).getClass().getDeclaredMethods()));
       }
       return result;
   }

    /**
     * 组装方法请求数据
     * @param method  待调用的方法的字符串形式
     * @param parameters 当前方法的所有传参
     * @param result  方法的执行结果,可以为空
     * @return
     */
   protected ExcuteMethodVo CalculationMethodParams(String method,Map<String, Object> parameters,Object result)
   {
       ExcuteMethodVo methodVo=new ExcuteMethodVo();
       //解析方法名
       String[] arr = method.split("\\(");
       if (arr.length != 2)
           throw new DataException("beForeMethodName规则错误,必须有()");
       //获取方法名
       methodVo.setMethodName(arr[0]);
       //解析方法参数名数组
       String[] argsArr = arr[1].replaceAll("\\)", "").trim().split(",");

       Class[] classList = new Class[argsArr.length];
       Object[] objectList = new Object[argsArr.length];
       for (int i = 0; i < argsArr.length; i++)
       {
           //把方法的返回结果放进去
           if(argsArr[i].equals("result") && null != result)
           {
               classList[i] = null == result?Object.class:result.getClass();
               objectList[i] =null == result?null:result;
               continue;
           }
           classList[i] = null==parameters.get(argsArr[i])?Object.class:parameters.get(argsArr[i]).getClass();
           objectList[i] = null==parameters.get(argsArr[i])?null:parameters.get(argsArr[i]);
       }

       methodVo.setClasss(classList);
       methodVo.setObjects(objectList);
       return methodVo;
   }
    /**
     * 校验是否可以执行
     * @param that
     * @param script
     * @return
     */
    protected Boolean isExecuteScript(Class<?> that,Class<?> script)
    {
        if(script.getInterfaces().length > 1 )
            throw new ScriptException("本框架的脚本不支持多实现");
        if(script.getInterfaces().length <= 0 )
            throw new ScriptException("该脚本没实现任何脚本接口");
        //获取脚本接口的注解
        SuportService suportService=script.getAnnotation(SuportService.class);
        if(null == suportService)
            throw new ScriptException("您的自定义脚本["+script.getSimpleName()+"]没用@SuportService指定支持的service");
        return ClassHelper.isImplement(suportService.service(),that);
    }


}
